/*
 * Copyright (C) 2010 Peter Schönhofen
 *
 * This file is part of the Akkad Compiler Framework.
 *
 * The Akkad Compiler Framework is free software: you can redistribute it and/or
 * modify it under the terms of the GNU General Public License as published by
 * the Free Software Foundation, either version 3 of the License, or (at your
 * option) any later version.
 *
 * This program is distributed in the hope that it will be useful, but WITHOUT
 * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS
 * FOR A PARTICULAR PURPOSE. See the GNU General Public License for more details.
 *
 * You should have received a copy of the GNU General Public License along with
 * this program. If not, see <http://www.gnu.org/licenses/>.
 */

package hu.anaxo.akkad.processor.pl0

import hu.anaxo.akkad.processor._
import hu.anaxo.akkad.vm._
import collection.mutable.{HashMap, Map, Stack}

/**
 * PL/0 translator
 */

object PL0Translator extends Translator {

  /**
   * Generic symbol table entry
   */

  abstract class Entry

  /**
   * Constant entry
   *
   * @param value value
   */

  case class ConstantEntry(value: Int) extends Entry

  /**
   * Variable entry
   *
   * @param address address
   */

  case class VariableEntry(address: Int) extends Entry

  /**
   * Procedure entry
   *
   * @param address address
   */

  case class ProcedureEntry(address: Label) extends Entry

  /**
   * Invalid entry
   */

  case class NotFound() extends Entry

  // scopes

  val scopes: Stack[Map[String, Entry]] = new Stack

  // main entry point

  val main: Label = new Label

  /**
   * Look up symbol of given type in scopes
   *
   * @param scopes scopes
   * @param name name
   * @param kind type
   *
   * @return scope index, entry
   */

  private def lookup(label: String, name: String, kinds: Class[_]*): (Int, Entry) = {
    for (index <- 0 to scopes.length - 1) {
      val entry = scopes(index).getOrElse(name, NotFound())
      if (kinds.contains(entry.getClass))
        return (scopes.length - 1 - index, entry)
    }
    throw Fault("unknown %s %s".format(label, name))
  }

  /**
   * Check declaration conflict
   */

  private def checkConflict(name: String) =
    if (scopes.top.contains(name))
      throw Fault("name %s already defined".format(name))

  /**
   * Translate syntax tree
   *
   * @param syntax syntax tree
   * @param scopes scopes
   *
   * @return instructions
   */

  protected def realTranslate(syntax: Syntax): List[Instruction] =
    syntax match {

      // process block

      case Block(declarations, statement) =>
        val outer = scopes.isEmpty
        scopes.push(new HashMap)

        // translate code

        var result = declarations.flatMap(translate) ++
              (if (outer) Some(Define(main)) else None) ++
              List(EN(scopes.length - 1)) ++
              translate(statement)

        // add code for dumping variables if necessary

        if (outer) {
          val addresses = scopes.top.valuesIterator.flatMap({
            case VariableEntry(address) =>
              Some(address).iterator
            case _ =>
              None.iterator
          }).toList.sortWith(_ < _)
          result ++= addresses.flatMap((address) =>
              List(CN(address), PR(), GV(0, address), PR()))
        }

        // finish

        result ++= List(RT(scopes.length - 1))
        scopes.pop
        result

      // process constant declaration

      case ConstantDef(name, value) =>
        checkConflict(name)
        scopes.top += name -> ConstantEntry(value)
        Nil

      // process variable declarations

      case VariableDefs(names) =>
        var address: Int = 0
        for (name <- names) {
          checkConflict(name)
          scopes.top += name -> VariableEntry(address)
          address += 1
        }
        Nil

      // process procedure declaration

      case ProcedureDef(name, body) =>
        checkConflict(name)
        val label = new Label
        scopes.top += name -> ProcedureEntry(label)
        List(Define(label)) ++ translate(body)

      // process assignment

      case Assign(name, value) =>
        val (index, entry: VariableEntry) = lookup("variable", name, classOf[VariableEntry])
        translate(value) ++ List(SV(index, entry.address))

      // process procedure call

      case Call(name) =>
        val (index, entry: ProcedureEntry) = lookup("procedure", name, classOf[ProcedureEntry])
        List(CL(entry.address))

      // process statements

      case Statements(statements) =>
        statements.flatMap(translate)

      // process IF

      case If(condition, body) =>
        val label = new Label
        translate(condition) ++ List(JF(label)) ++ translate(body) ++ List(Define(label))

      // process WHILE

      case While(condition, body) =>
        val label1 = new Label
        val label2 = new Label
        List(Define(label1)) ++ translate(condition) ++ List(JF(label2)) ++ translate(body) ++
            List(JP(label1)) ++ List(Define(label2))

      // process negation

      case Negate(value) =>
        translate(value) ++ List(NG())

      // process addition

      case Add(left, right) =>
        translate(left) ++ translate(right) ++ List(AD())

      // process subtraction

      case Subtract(left, right) =>
        translate(left) ++ translate(right) ++ List(SB())

      // process multiplication

      case Multiply(left, right) =>
        translate(left) ++ translate(right) ++ List(ML())

      // process division

      case Divide(left, right) =>
        translate(left) ++ translate(right) ++ List(DV())

      // process value

      case Value(value) =>
        List(CN(value))

      // process variable or constant reference

      case Reference(name) =>
        val (index, entry) =
          lookup("constant or variable", name, classOf[ConstantEntry], classOf[VariableEntry])
        entry match {
          case ConstantEntry(value) =>
            List(CN(value))
          case VariableEntry(address) =>
            List(GV(index, address))
        }

      // process ODD test

      case Odd(value) =>
        translate(value) ++ List(OD())

      // process equal test

      case Equal(left, right) =>
        translate(left) ++ translate(right) ++ List(EQ())

      // process less test

      case Less(left, right) =>
        translate(left) ++ translate(right) ++ List(LT())

      // process greater test

      case Greater(left, right) =>
        translate(left) ++ translate(right) ++ List(GT())

      // process not equal test

      case NotEqual(left, right) =>
        translate(left) ++ translate(right) ++ List(NE())

      // process less or equal test

      case LessOrEqual(left, right) =>
        translate(left) ++ translate(right) ++ List(LE())

      // process greater or equal test

      case GreaterOrEqual(left, right) =>
        translate(left) ++ translate(right) ++ List(GE())
    }

  /**
   * Translate syntax tree
   *
   * @param syntax syntax tree
   *
   * @return executable program
   */

  def process(syntax: Syntax): List[Instruction] = resolve(List(JP(main)) ++ translate(syntax))
}